package de.global42.clubapp.ejbs;

import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

@Stateless
public class EntityAccessorBean implements EntityAccessor {

	private EntityManager entityManager;
	
	@Override
	@TransactionAttribute(TransactionAttributeType.SUPPORTS)
	public <T> T getEntity(Class<T> entityClass, String attributeName, String attributeValue)
		throws
			IllegalArgumentException,
			IllegalStateException,
			NoResultException,
			NonUniqueResultException,
			Exception
	{
		try {
			CriteriaQuery<T> criteriaQuery = getCriteriaQuery(entityClass, attributeName, attributeValue);
			TypedQuery<T> entityQuery = entityManager.createQuery(criteriaQuery);
			return entityQuery.getSingleResult();
		}catch (IllegalStateException e) {
			throw new IllegalStateException(e);
		}catch (IllegalArgumentException e) {
			throw new IllegalArgumentException(e);
		}catch (NoResultException e) {
			throw new NoResultException(e.getMessage());
		}catch (NonUniqueResultException e) {
			throw new NonUniqueResultException(e.getMessage());
		}catch (Exception e) {
			throw new Exception(e);
		}
	}

	private <T> CriteriaQuery<T> getCriteriaQuery(Class<T> entityClass, String attributeName, String attributeValue) throws IllegalArgumentException, IllegalStateException {
		CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
		CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
		Root<T> queryRoot = criteriaQuery.from(entityClass);
		
		return criteriaQuery.select(queryRoot).distinct(true).where(criteriaBuilder.equal(queryRoot.get(attributeName), attributeValue));
	}
	
	@Override
	public <T> T getEntity(Class<T> entityClass, String[] attributeNames, String[] attributeValues) 
		throws 
			NoResultException,
			NonUniqueResultException, 
			IllegalStateException,
			IllegalArgumentException,
			Exception {
		try {
			CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
			CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
			Root<T> queryRoot = criteriaQuery.from(entityClass);
			Set<Predicate> restrictions = getRestrictions(attributeNames,attributeValues, criteriaBuilder, queryRoot);
			criteriaQuery.select(queryRoot).distinct(true).where((Predicate[]) restrictions.toArray());
			
			TypedQuery<T> query = entityManager.createQuery(criteriaQuery);
			return query.getSingleResult();
		} catch (IllegalStateException e) {
			throw new IllegalStateException(e);
		}catch (IllegalArgumentException e) {
			throw new IllegalArgumentException(e);
		}catch (NoResultException e) {
			throw new NoResultException(e.getMessage());
		}catch (NonUniqueResultException e) {
			throw new NonUniqueResultException(e.getMessage());
		}catch (Exception e) {
			throw new Exception(e);
		}
	}

	@Override
	public <T> T getEntity(Class<T> entityClass, Map<String, String> nameValuePairs)
		throws 
			NoResultException,
			NonUniqueResultException, 
			IllegalStateException,
			IllegalArgumentException,
			Exception {
		try {
			CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
			CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
			Root<T> queryRoot = criteriaQuery.from(entityClass);
			Set<Predicate> restrictions = getRestrictions(nameValuePairs, criteriaBuilder, queryRoot);
			criteriaQuery.select(queryRoot).distinct(true).where((Predicate[]) restrictions.toArray());
			
			TypedQuery<T> query = entityManager.createQuery(criteriaQuery);
			return query.getSingleResult();
		} catch (IllegalStateException e) {
			throw new IllegalStateException(e);
		}catch (IllegalArgumentException e) {
			throw new IllegalArgumentException(e);
		}catch (NoResultException e) {
			throw new NoResultException(e.getMessage());
		}catch (NonUniqueResultException e) {
			throw new NonUniqueResultException(e.getMessage());
		}catch (Exception e) {
			throw new Exception(e);
		}
	}

	private <T> Set<Predicate> getRestrictions(String[] attributeNames, String[] attributeValues, CriteriaBuilder criteriaBuilder, Root<T> queryRoot) throws IllegalArgumentException {
		if(attributeNames.length != attributeValues.length){
			throw new IllegalArgumentException("attributesNames and attributeValues need to be of the same length!");
		}
		else{
			Set<Predicate> restrictions = new HashSet<Predicate>();
			for (int i = 0; i < attributeValues.length; i++) {
				restrictions.add(criteriaBuilder.equal(queryRoot.get(attributeNames[i]), attributeValues[i]));
			}
			return restrictions;
		}
	}
	
	private <T> Set<Predicate> getRestrictions(Map<String, String> nameValuePairs, CriteriaBuilder criteriaBuilder, Root<T> queryRoot) throws IllegalArgumentException {
		Set<Predicate> restrictions = new HashSet<Predicate>();
		for (Entry<String, String> nameValuePair : nameValuePairs.entrySet()) {
			restrictions.add(criteriaBuilder.equal(queryRoot.get(nameValuePair.getKey()), nameValuePair.getValue()));
		}
		return restrictions;
	}

	@PersistenceContext
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}
}
